//
// Created by ta&to on 2022/9/28.
//

#include "http_connect.h"
#ifdef SOCKET_SERVER_EPOLL

static void _init_epoll(log_t *logt, struct http_socket_event_loop_s *hset){
    hset->current_link = 0;
    hset->epoll_ev_fds = (struct epoll_event *) http_malloc(hset->max_link * sizeof(struct epoll_event));
}
static void _create_epoll(log_t *logt, struct http_socket_event_loop_s *hset) {
    /************************
    返回一个epoll文件描述符，size表示epoll能监听的最大连接数
    从linux2.6.8开始，size参数被忽略，必须大于零
    在最初中，是提示内核，给其分配多大的内存，当然，在初始中，如果超过了，内核也将分配更多的空间
    现在，这个提示不再需要，内核会动态的分配空间，根据需要调整数据结构的大小
    但是大小必须大于0,以便于在较旧的内核上运行时，向后兼容。
    *************************/
    hset->epoll_fd = epoll_create(1);
    if(hset->epoll_fd < 0)
    {
        error(logt, "epoll create error[%s]", strerror(errno));
        exit(EXIT_FAILURE);
    }
}
static void _add_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd, uint32_t events) {
    /*********************
    epoll注册函数，select是在监听事件时告诉内核要监听什么类型的事件，这里是先注册要监听的事件类型
    epfd：epoll_create(int size)函数的返回值，
    op: 表示动作，添加删除修改
        EPOLL_CTL_ADD：注册新的fd到epfd中；
        EPOLL_CTL_MOD：修改已经注册的fd的监听事件；
        EPOLL_CTL_DEL：从epfd中删除一个fd；
    fd: 要监听的连接描述符，注册fd到epfd这个池子中
    typedef union epoll_data {
        void *ptr;
        int fd;
        __uint32_t u32;
        __uint64_t u64;
    } epoll_data_t;
    struct epoll_event {
        __uint32_t events; // Epoll events 要监听的事件类型
        epoll_data_t data; // User data variable 在这里，我们使用联合体中的fd，表示要监听连接描述符
    };
    events描述要监听的事件类型，可以是以下的组合
    EPOLLIN ：表示对应的文件描述符可以读（包括对端SOCKET正常关闭）；
    EPOLLOUT：表示对应的文件描述符可以写；
    EPOLLPRI：表示对应的文件描述符有紧急的数据可读（这里应该表示有带外数据到来）；
    EPOLLERR：表示对应的文件描述符发生错误；
    EPOLLHUP：表示对应的文件描述符被挂断；
    EPOLLET： 将EPOLL设为边缘触发(Edge Triggered)模式，这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT：只监听一次事件，当监听完这次事件之后，如果还需要继续监听这个socket的话，需要再次把这个socket加入到EPOLL队列里
    *********************/
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = events;                    // 这里只处理读和退出
    if(epoll_ctl(hset->epoll_fd, EPOLL_CTL_ADD, fd, &ev) == -1)//往内核事件表中注册fd上的事件
    {
        error(logt, "epoll_ctl add error [%s]", strerror(errno));
        exit(EXIT_FAILURE);
    }
    atomicIncr(hset->current_link, 1);
}
static void _mod_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd, uint32_t events) {
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = events;                    // 这里只处理读和退出
    if(epoll_ctl(hset->epoll_fd, EPOLL_CTL_MOD, fd, &ev) == -1)//往内核事件表中注册fd上的事件
    {
        error(logt, "epoll_ctl mod error [%s]", strerror(errno));
        exit(EXIT_FAILURE);
    }
    atomicIncr(hset->current_link, 1);
}
int _del_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd)
{
    if(epoll_ctl(hset->epoll_fd, EPOLL_CTL_DEL, fd, NULL) == -1)
    {
        error(logt, "epoll_ctl del error:[%s]", strerror(errno));
        return thread_err;
    }
    atomicDecr(hset->current_link, 1);
    return fd;
}
void init_epoll(log_t *logt, struct http_socket_event_loop_s *hset){
    _init_epoll(logt, hset);
}
int create_epoll(log_t *logt, struct http_socket_event_loop_s *hset){
    _create_epoll(logt, hset);
}
void add_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd, uint32_t events) {
    _add_event(logt, hset, fd, events);
}
void mod_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd, uint32_t events) {
    _mod_event(logt, hset, fd, events);
}
int del_event(log_t *logt, struct http_socket_event_loop_s *hset, int fd) {
    return _del_event(logt, hset, fd);
}
static void _fd_handler(log_t *logt, struct http_socket_event_loop_s *hset, int wait) {
    for (int i = 0; i < wait; ++i) {
        struct epoll_event ev = hset->epoll_ev_fds[i];
        if(ev.data.fd == -1)
            continue;
        if(ev.data.fd == hset->socket_fd){      // 特殊处理 得到一个新的客户端链接
            http_socket_event_handle_t * handler = copy_func_handler(hset);
            int client_fd = conn_handler(logt, hset, handler);

            mutex_lock(&hset->hgdefc->event_fun_lock);
            char *fdc = (char *) http_calloc(sizeof(char) * http_numlen(client_fd) + 1);
            memset(fdc, 0, sizeof(fdc));
            http_sprintf(fdc,   "%d", client_fd);
            do{             // 只要有修改的可能就要删除再添加，防止野指针问题
                RBtreenodedel(&hset->hgdefc->func_handler, fdc);
            } while (!RBtreenodeadd(&hset->hgdefc->func_handler, fdc, handler));
            http_free(fdc);
            mutex_unlock(&hset->hgdefc->event_fun_lock);

//            如果当前的IO接口工作于阻塞模式，那么当一个事件被阻塞的时候，其他事件的就绪也就只会被通知一次但并得不到处理，因此会导致多数据的堆积，所以，当使用边缘式触发的时候：
//            1、最好将当前的IO接口设定为非阻塞的；
//            2、当一个IO事件进行数据的读取和写入的时候，最好一次性就将缓冲区中的数据全部都处理完；因此，对于数据的读取，可以用一个循环来每次读取特定的长度，当最后一次读取的长度小于特定的长度时，就可以认为当前缓冲区的数据已经全部读取完毕终止循环；但是，不可避免的是，如果最后一次的读取恰好也就是特定的长度，那么在此进行读取缓冲区中数据为0，就会返回一个EAGAIN的错误码，这个就可以作为循环的终止条件；
          //  socketNonBlock(logt, client_fd);
            add_event(logt, hset, client_fd, EPOLLIN|EPOLLET);
        }else if(ev.events & EPOLLIN)//EPOLLIN事件，连接到达；有数据来临；
        {
            debug(logt, "_fd_handler()检查到[%d]有数据到达", ev.data.fd);
            hset->current_fd = ev.data.fd;

            mutex_lock(&hset->hgdefc->event_fun_lock);
            char *fdc = (char *) http_calloc(sizeof(char) * http_numlen(ev.data.fd) + 1);
            memset(fdc, 0, sizeof(fdc));
            http_sprintf(fdc, "%d", ev.data.fd);
            noder node = RBtreenodesel(&hset->hgdefc->func_handler, fdc);
            if(ISNULL(node)){
                mutex_unlock(&hset->hgdefc->event_fun_lock);
                continue;
            }
            http_socket_event_handle_t * handler = (http_socket_event_handle_t *)node->spot->value;
            http_free(fdc);
            mutex_unlock(&hset->hgdefc->event_fun_lock);

            if(ISNOTNULL(handler->func_read))
                handler->func_read(hset, del_event(logt, hset, ev.data.fd) );
        }else{
            error(logt, "ev.events status[0x%02XD] %s", ev.events, strerror(errno));
            del_event(logt, hset, ev.data.fd);
            close(ev.data.fd);
        }
    }
}

static int _epoll_poll(log_t *logt, struct http_socket_event_loop_s *hset) {
    /*********************
    epfd： 等待的监听描述符，也就是哪个池子中的内容
    *events： 指针，指向epoll_event的数组，监听描述符中的连接描述符就绪后，将会依次将信息填入
    maxevents： 最多返回最大事件
    timeout： 等待时间，要是有连接描述符就绪，立马返回，如果没有，timeout时间后也返回，单位是ms
    epoll_wait()将会一直阻塞直到：epfd产生事件 / 被信号处理函数打断 / 超时。
    *********************/
    while(1) {
        int wait = epoll_wait(hset->epoll_fd, hset->epoll_ev_fds, hset->max_link,
                         (ISNOTNULL(hset->tvp) ? (hset->tvp->tv_sec*1000 + (hset->tvp->tv_usec + 999)/1000) : -1));//一段超时事件内等待一组文件描述符上的事件
        if(wait == -1)
        {
            error(logt, "epoll_wait error [%s]", strerror(errno));
            continue;
        }
        else if(wait == 0)
        {
            warn(logt, "已超时\n");
            continue;
        }
        else {
            _fd_handler(logt, hset, wait);
        }
    }
}

void Epoll_do(log_t *logt, struct http_socket_event_loop_s *hset) {
    init_epoll(logt, hset);
    create_epoll(logt, hset);
    add_event(logt, hset, hset->socket_fd, EPOLLIN|EPOLLET);            // 添加socket fd到epoll
    _epoll_poll(logt, hset);
}
int Epoll_IO(log_t *logt, struct http_socket_event_loop_s *hset) {
#if !defined(__linux__)
    error(logt, "Platform no Support Epoll");
#else
    Epoll_do(logt, hset);
#endif
}
#endif